
//this class creates the abstract syntax tree
public class MiniReRDP {
	private Scanner s;
    public Node node;
    private boolean debug = false;
    private Integer nodeNumber = 0; //this is for checking the tree for correctness
	
	public MiniReRDP(Scanner scan)
	{
		this.s = scan;
        node = MiniRE_program(); //start parsing it
	}
	
    public Node getNode(){ //return root
        return node;
    }

   public Node MiniRE_program() {
        Node n = new Node(nextAvailableNodeNumber(), null);
        if(debug)
        {
        	System.out.println("in MiniRE_program");
        }

        // Match begin
        Token temp = s.peek();
        if(!matchType(TokenClass.BEGIN))
        {
        	parseError(temp);     	
        }
        
        // Go parse the statement_list
        n.addChild(statement_list(n));
        
        // Match end
        temp = s.peek();
        if(!matchType(TokenClass.END))
        {
        	parseError(temp);     	
        }
        if(debug)
        {
        	System.out.println("out MiniRE_program");
        }
        return n;
    }

   public Node statement_list(Node parent) {
        Node n = new Node(nextAvailableNodeNumber(), parent);
        if(debug)
        {
        	System.out.println("in statement_list");
        }
        // Go parse the statement
        n.addChild(statement(n));
        
        // Go parse the statement_list_tail
        n.addChild(statement_list_tail(n));
        if(debug)
        {
        	System.out.println("out statement_list");
        }
        return n;
    }

   public Node statement_list_tail(Node parent) {
        Node n = new Node(nextAvailableNodeNumber(), parent);
        if(debug)
        {
        	System.out.println("in statement_list_tail");
        }
        //makes sure we are at the start of a statement, otherwise return an empty node
        if(s.peek().getType() == TokenClass.ID || s.peek().getType() == TokenClass.REPLACE || s.peek().getType() == TokenClass.RECURSIVEREPLACE || s.peek().getType() == TokenClass.PRINT){
            n.addChild(statement(n));
            n.addChild(statement_list_tail(n));
        }
        if(debug)
        {
        	System.out.println("out statement_list_tail");
        }
        return n;
    }

   public Node file_names(Node parent) {
        Node n = new Node(nextAvailableNodeNumber(), parent);
        if(debug)
        {
        	System.out.println("in file_names");
        }
        n.addChild(source_file(n));
        //grab this token ahead of time so we can use matchTypeOrDie
        n.setToken(s.peek());
        matchTypeOrDie(TokenClass.REDIRECT);
        n.addChild(destination_file(n));
        if(debug)
        {
        	System.out.println("out file_names");
        }
        return n;
    }

   public Node source_file(Node parent) {
        Node n = new Node(nextAvailableNodeNumber(), parent);
        if(debug)
        {
        	System.out.println("in source_file");
        }
        //grab this token ahead of time so we can use matchTypeOrDie
        n.setToken(s.peek());
        matchTypeOrDie(TokenClass.ASCIISTR);
        if(debug)
        {
        	System.out.println("out source_file");
        }
        return n;
    }

   public Node destination_file(Node parent) {
        Node n = new Node(nextAvailableNodeNumber(), parent);
        if(debug)
        {
        	System.out.println("in destination_file");
        }
        n.setToken(s.peek());
        //grab this token ahead of time so we can use matchTypeOrDie
        matchTypeOrDie(TokenClass.ASCIISTR);
        if(debug)
        {
        	System.out.println("out destination_file");
        }
        return n;
    }

   public Node statement(Node parent) {
        Node n = new Node(nextAvailableNodeNumber(), parent);
        if(debug)
        {
        	System.out.println("in statement");
        }
        
        // Determine if we're doing cases 0-2
        if(s.peek().getType() == TokenClass.ID)
        {
        	// Go match the ID        	
        	// Store the first ID in the token variable of the first child NODE
        	Node temp = new Node(nextAvailableNodeNumber(), n);
        	temp.setToken(s.peek());
        	n.addChild(temp);
        	
        	// Eat the ID token
        	matchType(TokenClass.ID);
        	// Eat the Equal token
        	matchType(TokenClass.EQUALS);
        	
        	// Determine if we're doing case 1
        	if(s.peek().getType() == TokenClass.HASH)
        	{
        		// Store the Operator data in this node's token variable
        		n.setToken(s.peek());
        		
        		// Eat the Operator 
        		matchType(TokenClass.HASH);
        		
        		// Go parse the expression
        		n.addChild(exp(n));
        		
        		// Eat the semicolon
        		matchTypeOrDie(TokenClass.SEMICOLON);
        	}
        	// Determine if we're doing case 2
        	else if(s.peek().getType() == TokenClass.MAXFREQSTR)
        	{
                System.out.println("asdf");
        		// Store the Operator in this node's token variable
        		n.setToken(s.peek());
        		// Eat the operator
        		matchType(TokenClass.MAXFREQSTR);
        		
        		// Match the open paren
        		matchTypeOrDie(TokenClass.OPENPAR);
        		
        		// Store the second ID as a child node to be used in execution
            	Node temp2 = new Node(nextAvailableNodeNumber(), n);
            	temp2.setToken(s.peek());
            	n.addChild(temp2);
            	
            	// Eat the ID
        		matchTypeOrDie(TokenClass.ID);
        		
        		// Eat the close paren
        		matchTypeOrDie(TokenClass.CLOSEPAR);
        		
        		// Eat the semicolon
        		matchTypeOrDie(TokenClass.SEMICOLON);
        	}
        	// If we're not doing case 1 or 2 after seeing an ID it has to be case 0
        	else
        	{
                //Do this so the node contains an equals sign
                s.replace();
                n.setToken(s.get());
        		// Go parse the expression
        		n.addChild(exp(n));
        		
        		// Eat the semicolon
        		matchTypeOrDie(TokenClass.SEMICOLON);
        	}
        	
        }
        // Determine if we're doing case 3
        else if(s.peek().getType() == TokenClass.REPLACE)
        {
        	// Store the replace operator on this node as the token
        	n.setToken(s.peek());
        	// Eat the operator
        	matchType(TokenClass.REPLACE);
        	
        	// Store the regex as a child node to be used in execution
        	Node temp = new Node(nextAvailableNodeNumber(), n);
        	temp.setToken(s.peek());
        	n.addChild(temp);
        	
        	// Eat the regex
        	matchTypeOrDie(TokenClass.REGEX);
        	
        	// Eat the WITH keyword
        	matchTypeOrDie(TokenClass.WITH);
        	
        	// Store the ASCII String as a child node to be used in execution
        	Node temp2 = new Node(nextAvailableNodeNumber(), n);
        	temp2.setToken(s.peek());
        	n.addChild(temp2);
        	
        	// Eat the ASCII String
        	matchTypeOrDie(TokenClass.ASCIISTR);
        	
        	// Eat the IN keyword
        	matchTypeOrDie(TokenClass.IN);
        	
        	// Go parse the filenames 
        	n.addChild(file_names(n));
        	
        	// Eat the semicolon
        	matchTypeOrDie(TokenClass.SEMICOLON);       	
        }
        // Determine if we're doing case 4
        else if(s.peek().getType() == TokenClass.RECURSIVEREPLACE)
        {
        	// Store the recursiveReplace operator on this node as the token
        	n.setToken(s.peek());
        	// Eat the operator
        	matchType(TokenClass.RECURSIVEREPLACE);
        	
        	// Store the regex as a child node to be used in execution
        	Node temp = new Node(nextAvailableNodeNumber(), n);
        	temp.setToken(s.peek());
        	n.addChild(temp);
        	
        	// Eat the regex
        	matchTypeOrDie(TokenClass.REGEX);
        	
        	// Eat the WITH keyword
        	matchTypeOrDie(TokenClass.WITH);
        	
        	// Store the ASCII String as a child node to be used in execution
        	Node temp2 = new Node(nextAvailableNodeNumber(), n);
        	temp2.setToken(s.peek());
        	n.addChild(temp2);
        	
        	// Eat the ASCII String
        	matchTypeOrDie(TokenClass.ASCIISTR);
        	
        	// Eat the IN keyword
        	matchTypeOrDie(TokenClass.IN);
        	
        	// Go parse the filenames 
        	n.addChild(file_names(n));
        	
        	// Eat the semicolon
        	matchTypeOrDie(TokenClass.SEMICOLON);        	
        }
        // If we're not in cases 0-4 we have to be in case 5, print
        else
        {
        	// Store the print operator to be used in execution
        	n.setToken(s.peek());
        	// Eat the operator
        	matchType(TokenClass.PRINT);
        	
        	// Eat the open paren
        	matchTypeOrDie(TokenClass.OPENPAR);
        	
        	// Add expressionlist to child node
        	n.addChild(exp_list(n));
        	
        	// Eat the close paren
        	matchTypeOrDie(TokenClass.CLOSEPAR);
        	
        	// Eat the semicolon
        	matchTypeOrDie(TokenClass.SEMICOLON);        	
        }
        if(debug)
        {
        	System.out.println("out statement");
        }
        return n;
    }

   public Node exp_list(Node parent) {
        Node n = new Node(nextAvailableNodeNumber(), parent);
        if(debug)
        {
        	System.out.println("in exp_list");
        }
        n.addChild(exp(n));
        n.addChild(exp_list_tail(n));
        if(debug)
        {
        	System.out.println("out exp_list");
        }
        return n;
    }

   public Node exp_list_tail(Node parent) {
        Node n = new Node(nextAvailableNodeNumber(), parent);
        if(debug)
        {
        	System.out.println("in exp_list_tail");
        }
        //check to see if we should stop recursing
        if(s.peek().getType() == TokenClass.COMMA){
            //throw away the comma
            s.get();
            n.addChild(exp(n));
            //recurse
            n.addChild(exp_list_tail(n));
        }
        if(debug)
        {
        	System.out.println("out exp_list_tail");
        }
        return n;
    }


    public Node exp(Node parent) {
        Node n = new Node(nextAvailableNodeNumber(), parent);
        if(debug)
        {
        	System.out.println("in exp");
        }
        //case 1
        if(s.peek().getType() == TokenClass.ID){
            n.setToken(s.get());
        //case 2
        }else if(s.peek().getType() == TokenClass.OPENPAR){
            //throw away the ('s
            matchTypeOrDie(TokenClass.OPENPAR);
            //recurse
            n.addChild(exp(n));
            matchTypeOrDie(TokenClass.CLOSEPAR);
        //case 3
        }else if(s.peek().getType() == TokenClass.FIND){
                n.addChild(term(n));
                n.addChild(exp_tail(n));               
        //case 4
        }else{//if we get here, we are in the second type of exp
            n.addChild(term(n));
            n.addChild(exp_tail(n));
        }
        if(debug)
        {
        	System.out.println("out exp");
        }
        return n;
    }

    public Node exp_tail(Node parent) {

        Node n = new Node(nextAvailableNodeNumber(), parent);
        if(debug)
        {
        	System.out.println("in exp_tail");
        }
        //check to see if bin_op is coming up
        if(s.peek().getType() == TokenClass.DIFF || s.peek().getType() == TokenClass.UNION || s.peek().getType() == TokenClass.INTERS){
            //record what binary op we get
            n.setToken(s.get());
            n.addChild(exp(n));
            //recurse
            n.addChild(exp_tail(n));
        }
        //if we get here, treat it as an epsilon
        if(debug)
        {
        	System.out.println("out exp_tail");
        }
        return n;
    }

    public Node term(Node parent) {

        Node n = new Node(nextAvailableNodeNumber(), parent);
        if(debug)
        {
        	System.out.println("in term");
        }
        //set FIND before matchTypeOrDie
        n.setToken(s.peek());
        matchTypeOrDie(TokenClass.FIND);
        //set the regex before matchTypeOrDie
        n.addChild(new Node(s.peek(),nextAvailableNodeNumber(), n));
        matchTypeOrDie(TokenClass.REGEX);
        //just throw this away
        matchTypeOrDie(TokenClass.IN);
        //get the file name
        n.addChild(file_name(n));
            
        if(debug)
        {
        	System.out.println("out term");
        }
        return n;
    }

    public Node file_name(Node parent) {

        Node n = new Node(nextAvailableNodeNumber(), parent);
        if(debug)
        {
        	System.out.println("in file_name");
        }
        //get the STR before MatchTypeOrDie
        n.setToken(s.peek());
        matchTypeOrDie(TokenClass.ASCIISTR);
        if(debug)
        {
        	System.out.println("out file_name");
        }
        return n;

    }

    public Node bin_op(Node parent) {

        Node n = new Node(nextAvailableNodeNumber(), parent);
        if(debug)
        {
        	System.out.println("in bin_op");
        }
        //check to make sure we actually have a bin_op
        if(s.peek().getType() == TokenClass.DIFF || s.peek().getType() == TokenClass.UNION || s.peek().getType() == TokenClass.INTERS){
            n.setToken(s.get());
        }
        else
        {
            //throw a parseError
            parseError(s.peek());
        }
        if(debug)
        {
        	System.out.println("out bin_op");
        }
        return n;
    }

    public boolean matchType(TokenClass type){
        return s.get().getType() == type;
    }

    //this is used to throw an error easily if nessicary, must get the token first if we need to keep it in the node
    public boolean matchTypeOrDie(TokenClass type){
        if(s.peek().getType() == type){
            s.get();
            return true;
        }
        else
        {
            parseError(s.get(),type);
            return false;
        }
    }


    //Make a nice looking error message
    public void parseError(Token t, TokenClass type){
        System.out.println("Parse error on token: " + t);
        System.out.println("Expected:" + type);
        System.exit(1);
    }

    //deprecated, see above
    public void parseError(Token t){
        System.out.println("Parse error on token: " + t);
        System.exit(1);
    }
    
    //used for bookkeeping to check if the tree is generated correctly
    public Integer nextAvailableNodeNumber()
    {
    	Integer temp = nodeNumber;
    	nodeNumber += 1;
    	return temp;    	
    }
}
